package com.lagou.edu.factory;

import com.lagou.edu.anotation.*;
import com.lagou.edu.servlet.MyTransferServlet;
import com.lagou.edu.utils.TransactionManager;
import com.mysql.jdbc.jmx.LoadBalanceConnectionGroupManager;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.net.URL;
import java.util.*;


/**
 * @author 应癫
 *
 * 工厂类，生产对象（使用反射技术）
 */
public class BeanFactory {

    /**
     * 任务一：读取解析xml，通过反射技术实例化对象并且存储待用（map集合）
     * 任务二：对外提供获取实例对象的接口（根据id获取）
     */
    private static Map<String,Object> map = new HashMap<>();  // 存储对象


    private static Logger log = LoggerFactory.getLogger(BeanFactory.class);

    /**
     * 使用XML进行初始化，相当于XMLApplicationContext
     */
    public static  void  initByXML(MyTransferServlet servlet) {

        // 任务一：读取解析xml，通过反射技术实例化对象并且存储待用（map集合）
        // 加载xml
        InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
        // 解析xml
        SAXReader saxReader = new SAXReader();
        try {
            Document document = saxReader.read(resourceAsStream);
            Element rootElement = document.getRootElement();
            List<Element> beanList = rootElement.selectNodes("//bean");
            for (int i = 0; i < beanList.size(); i++) {
                Element element =  beanList.get(i);
                // 处理每个bean元素，获取到该元素的id 和 class 属性
                String id = element.attributeValue("id");        // accountDao
                String clazz = element.attributeValue("class");  // com.lagou.edu.dao.impl.JdbcAccountDaoImpl
                // 通过反射技术实例化对象
                Class<?> aClass = Class.forName(clazz);
                Object o = aClass.newInstance();  // 实例化之后的对象

                // 存储到map中待用
                map.put(id,o);

            }

            // 实例化完成之后维护对象的依赖关系，检查哪些对象需要传值进入，根据它的配置，我们传入相应的值
            // 有property子元素的bean就有传值需求
            List<Element> propertyList = rootElement.selectNodes("//property");
            // 解析property，获取父元素
            for (int i = 0; i < propertyList.size(); i++) {
                Element element =  propertyList.get(i);   //<property name="AccountDao" ref="accountDao"></property>
                String name = element.attributeValue("name");
                String ref = element.attributeValue("ref");

                // 找到当前需要被处理依赖关系的bean
                Element parent = element.getParent();

                // 调用父元素对象的反射功能
                String parentId = parent.attributeValue("id");
                Object parentObject = map.get(parentId);
                // 遍历父对象中的所有方法，找到"set" + name
                Method[] methods = parentObject.getClass().getMethods();
                for (int j = 0; j < methods.length; j++) {
                    Method method = methods[j];
                    if(method.getName().equalsIgnoreCase("set" + name)) {  // 该方法就是 setAccountDao(AccountDao accountDao)
                        method.invoke(parentObject,map.get(ref));
                    }
                }

                // 把处理之后的parentObject重新放到map中
                map.put(parentId,parentObject);

            }

        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }




    /**
     * 使用Anotation进行初始化，相当于AnotationApplicationContext
     */
    public static void initByAnotation(MyTransferServlet servlet,String packageName) {
        try {
            log.debug("开始执行加载");

            //1  创建类
            createBean( packageName);



            //2 由于servlet不是我们管理的，因此需要将它单独放进来
            map.put(MyTransferServlet.class.getName(),servlet);

            //3 根据注解自动装配
            autowired(map);

            //4 类级别动态代理
            transaction(map);

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }


    //获取注解上的属性值
    private static String getValue(Annotation annotation,String propertyName) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
        Method method = annotation.annotationType().getDeclaredMethod(propertyName);
        String value = (String)method.invoke(annotation);
        return value;
    }


    public static void createBean(String packageName) throws IOException, ClassNotFoundException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException {
        List<Class<?>> classList = getClasses(packageName);
        for (Class clazz:classList){
            log.debug(clazz.getName());
            //1 根据annotation创建类
            Annotation[] annotations = clazz.getAnnotations();
            if(annotations!=null&&annotations.length>0){
                for(Annotation annotation:annotations){
                    log.debug("具有annotations的类:{},注解值:{}",clazz.getName(),annotation.annotationType().getName());
                    Class annotationClazz = annotation.annotationType();
                    if(annotationClazz.equals(Service.class)||
                            annotationClazz.equals(Repository.class)||
                            annotationClazz.equals(Component.class)
                    ){
                        String value = getValue(annotation,"value");
                        log.debug("具有annotations的类:{},注解值:{},参数:{}",clazz.getName(),annotation.annotationType().getName(),value);
                        String beanId = clazz.getName();
                        // 根据配置设置bean的名称
                        if(value.length()>0){
                            beanId = beanId +"#"+ value;
                        }
                        // 还没创建过的对象
                        if(!map.containsKey(beanId)){
                            Object object = clazz.getDeclaredConstructor().newInstance();
                            map.put(beanId,object);
                            log.info("创建对象:{} map长度：{}",beanId,map.size());
                        }
                    }
                }
            }
        }
    }

    /**
     * 注入对象属性值
     * @param beanMap
     */
    public static void autowired(Map<String,Object> beanMap) throws ClassNotFoundException, IllegalAccessException {
        for(Map.Entry<String,Object> entry:beanMap.entrySet()){
            String beanId = entry.getKey();
            Object beanObject = entry.getValue();
            Class  beanClass = Class.forName(beanId.split("#")[0]);

            Field[] fields = beanClass.getDeclaredFields();
            for (Field field:fields){
                Autowired autowired = field.getAnnotation(Autowired.class);
                if(autowired!=null){
                    String value = autowired.value();
                    String filedBeanId = field.getType().getName();
                    if(value.length()>0){
                        filedBeanId =  filedBeanId +"#"+value;
                    }
                    // 1 如果直接能够找得到类
                    if(beanMap.containsKey(filedBeanId)){
                        field.setAccessible(true);
                        field.set(beanObject,beanMap.get(filedBeanId));
                        log.info("1 完全匹配自动装配:{},属性名:{},属性值:{}",beanClass.getSimpleName(),field.getName(),beanMap.get(filedBeanId));
                    }
                    // 2 使用id查找类
                    else if(value.length()>0){
                        boolean find = false;
                        //根据接口或者实现,从map中寻找
                        for (String tempBeanId:beanMap.keySet()){
                            if(tempBeanId.contains("#"+value)){
                                field.setAccessible(true);
                                field.set(beanObject,beanMap.get(tempBeanId));
                                log.info("2 id自动装配:{},属性名:{},属性值:{}",beanClass.getSimpleName(),field.getName(),beanMap.get(tempBeanId));
                                find = true;
                                break;
                            }
                        }

                        if(!find){
                            throw new  NullPointerException("属性注入错误，因为不存在属性名为: "+filedBeanId+" 的属性值");
                        }
                    }
                    // 3 使用接口定义，查找实现类
                    else{
                        boolean find = false;
                        Class fieldClass = field.getType();
                        //根据接口或者实现,从map中寻找
                        for (String tempBeanId:beanMap.keySet()){
                            Class tempBeanClass = Class.forName(tempBeanId.split("#")[0]);
                            //如果声明的类是存储中的父类
                            if(fieldClass.isAssignableFrom(tempBeanClass)){
                                field.setAccessible(true);
                                field.set(beanObject,beanMap.get(tempBeanId));
                                log.info("3 继承自动装配:{},属性名:{},属性值:{}",beanClass.getSimpleName(),field.getName(),beanMap.get(tempBeanId));
                                find = true;
                                break;
                            }
                        }
                        if(!find){
                            throw new  NullPointerException("属性注入错误，因为不存在属性名为: "+filedBeanId+" 的属性值");
                        }

                    }
                }
            }
        }
    }


    /**
     * 类级别的动态代理
     * */
    public static void  transaction(Map<String,Object> beanMap) throws ClassNotFoundException {

        // TransactionManager类存在才能进行封装呀
        if(getBean(TransactionManager.class)!=null){
            TransactionManager transactionManager = (TransactionManager)getBean(TransactionManager.class);
            for (String beanId:beanMap.keySet()){
                Class beanClass = Class.forName(beanId.split("#")[0]);
                Object obj =  beanMap.get(beanId);
                //对这个类进行动态代理
                if(beanClass.getAnnotation(Transactional.class)!=null){
                    Object proxy = null;
                     //实现了接口,使用JDK动态代理
                     if(beanClass.getInterfaces().length>0){
                         // 获取代理对象
                          proxy = Proxy.newProxyInstance(beanClass.getClassLoader(), beanClass.getInterfaces(),
                                 new InvocationHandler() {
                                     @Override
                                     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                                         Object result = null;
                                         try{

                                             log.info("使用JDK动态代理进行事务");
                                             // 开启事务(关闭事务的自动提交)
                                             transactionManager.beginTransaction();
                                             result = method.invoke(obj,args);
                                             // 提交事务
                                             transactionManager.commit();
                                         }catch (Exception e) {
                                             e.printStackTrace();
                                             // 回滚事务
                                             transactionManager.rollback();
                                             // 抛出异常便于上层servlet捕获
                                             throw e;
                                         }
                                         return result;
                                     }
                                 });
                     }else{
                       proxy = Enhancer.create(obj.getClass(), new MethodInterceptor() {
                                        @Override
                                        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                                            Object result = null;
                                            try{

                                                log.info("使用cglib动态代理进行事务");
                                                // 开启事务(关闭事务的自动提交)
                                                transactionManager.beginTransaction();

                                                result = method.invoke(obj,objects);

                                                // 提交事务

                                                transactionManager.commit();
                                            }catch (Exception e) {
                                                e.printStackTrace();
                                                // 回滚事务
                                                transactionManager.rollback();

                                                // 抛出异常便于上层servlet捕获
                                                throw e;

                                            }
                                            return result;
                                        }
                               });
                     }

                      beanMap.put(beanId,proxy);
                    }
                }
            }
    }

    /**
     * 获取类
     * */
    public static List<Class<?>> getClasses(String packageName)
            throws ClassNotFoundException, IOException {
        ClassLoader classLoader = Thread.currentThread()
                .getContextClassLoader();
        String path = packageName.replace(".", "/");
//        log.info("path:" + path);
        Enumeration<URL> resources = classLoader.getResources(path);
//        log.info("获取资源路径" + resources);
        ArrayList<Class<?>> classes = new ArrayList<Class<?>>();
        while (resources.hasMoreElements()) {
//            log.info("resources.hasMoreElements()");
            URL resource = resources.nextElement();
//            log.info(resource.getProtocol());
//            log.info(resource.getPath());

            String protocol = resource.getProtocol();

            if ("file".equals(protocol)) {
                File file = new File(resource.getFile());
                classes.addAll(findClasses(file, packageName));
            }
        }
        return classes;
    }

    /**
     * 查找类
     * */
    private static List<Class<?>> findClasses(File directory, String packageName)
            throws ClassNotFoundException {
//        log.info("directory.exists()=" + directory.exists());
//        log.info("directory.getName()=" + directory.getName());
        ArrayList<Class<?>> classes = new ArrayList<Class<?>>();

        if (!directory.exists()) {
            return classes;
        }

        File[] files = directory.listFiles();
        for (File file : files) {
            if (file.isDirectory()) {
                assert !file.getName().contains(".");
                classes.addAll(findClasses(file,
                        packageName + "." + file.getName()));
            } else if (file.getName().endsWith(".class")) {
                classes.add(Class.forName(packageName
                        + "."
                        + file.getName().substring(0,
                        file.getName().length() - 6)));
            }
        }
        return classes;

    }


    // 任务二：对外提供获取实例对象的接口（根据id获取）
    public static  Object getBean(String id) {
        return map.get(id);
    }

    // 通过类获取对象
    public static  Object getBean(Class clazz) throws ClassNotFoundException {
        for (String beanId:map.keySet()){
            Class beanClass = Class.forName(beanId.split("#")[0]);
            if(clazz.equals(beanClass)){
                return map.get(beanId);
            }
        }
        return null;
    }

}
